feat(types): spec-generated REST/RELAY TypedDicts + wired return types + GEN-FRESH gate#39
Draft
mjerris wants to merge 42 commits into
Draft
feat(types): spec-generated REST/RELAY TypedDicts + wired return types + GEN-FRESH gate#39mjerris wants to merge 42 commits into
mjerris wants to merge 42 commits into
Conversation
…n types + GEN-FRESH gate Adds field-level static types to the REST/RELAY surface, generated mechanically from the canonical specs (the same approach the TypeScript port uses), with zero runtime change and zero cross-port drift. - 15 generated modules (rest/namespaces/*_types_generated.py + relay/ protocol_types_generated.py): one TypedDict per openapi/relay-protocol schema + per-operation Request/Response aliases. 1468 types. Emitted by porting-sdk/scripts/generate_python_rest_types.py. - 160 REST methods across 20 namespaces wired to their generated return types (e.g. fabric list_versions -> CallFlowVersionListResponse), guided by the TS port. Imported under TYPE_CHECKING with string forward-refs: ZERO runtime cost (the generated module is never imported at runtime) and the method bodies are unchanged — they still return the raw server JSON dict. A TypedDict is a plain dict at runtime, so a differently-shaped server response is returned unchanged and never raises. - GEN-FRESH gate in run-ci.sh: `generate_python_rest_types.py --check` fails if any committed generated module no longer reproduces from its spec (mirror of the TS port's --check). Gates: mypy --config zero, ruff format + check clean, GEN-FRESH pass. All 9 ports remain drift=0 (the complex types are matched cross-port by the porting-sdk checker's generated-type normalization; dict-recording ports match via gen<->dict). Depends on porting-sdk: generator + checker rules + GEN-FRESH script must be on porting-sdk main first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 15 generated *_types_generated / protocol_types_generated modules add ~1500 type-only statements (imported solely under TYPE_CHECKING, never executed), which dragged total coverage from ~74% to 55% and tripped the 63% fail-under. They carry no testable runtime code and are policed by the GEN-FRESH gate, not by tests — omit them from coverage measurement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WIP checkpoint (#81): CrudResource[TList,TItem,TCreate,TUpdate] generic base; 26 REST resources bound to spec-generated types; AutoMaterializedWebhook.create honestly returns dict (intermediate orphan-create); coverage omits generated stubs. mypy clean, deprecation tests pass, 9/9 ports drift=0. NOTE: ruff check still flags forward-ref TYPE_CHECKING imports as unused (fix pending). Local checkpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The generated-type imports under TYPE_CHECKING are used only inside quoted forward-refs (return annotations + CRUD-base subscript bindings), which ruff's F401 can't see -> false 'unused import' + --fix strips them, breaking mypy. Scoped per-file-ignore (mypy + DRIFT gate prove the imports are real). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prepares the REST client for generated typed CRUD resources.
- CrudResource.create/update: drop the broken `**kwargs: TCreate/TUpdate`
(which typed each kwarg VALUE as a whole request dict) for an honest
`**kwargs: Any -> TItem` fallback. The closed typed shape lives on the
generated per-resource subclasses; the class-level CrudResource[...] binding
(untouched) is what publishes the real TCreate/TUpdate to the oracle, so the
structural crud_base{bind:[4]} is unchanged and all ports stay drift=0.
- Move FabricResource / FabricResourcePUT from the fabric namespace into _base
(they are generic CRUD bases) so the generated fabric_resources_generated
subclasses can inherit them without an import cycle. fabric.py re-imports
them; they remain importable from there. These intermediate generics are not
recorded in the oracle, so the move is drift-neutral.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…xtras) Wire the generated per-resource typed CRUD subclasses into the fabric namespace and update the tests for the now-enforced spec-required fields. - fabric_resources_generated.py (generated): 12 typed CRUD subclasses, one per full-CRUD fabric resource, each bound to its spec types with a closed create/update — explicit spec fields (required honored) + an explicit `extras` door, no **kwargs tail. The named-subclass shape the oracle records as a crud_base; the structural binding is unchanged so parity holds. - fabric.py: construct the generated subclasses (group-A: ai_agents, sip_gateways, the script/connector/endpoint families, webhooks) instead of bare FabricResource[...] instances. Remove AutoMaterializedWebhook + its deprecation warning — these SDKs are pre-release, so direct webhook create is just a normal operation (no back-compat to deprecate). - tests: the typed create now enforces spec-required fields at the signature, so the coverage suites pass complete bodies (per-resource _CREATE_BODY/_UPDATE_BODY maps); error tests send a valid body so the 422 comes from the server, not the client; webhook tests assert no deprecation warning. Add TestRequiredFieldEnforcement: each missing required field raises TypeError, a typo'd field is rejected, and `extras` merges unknown fields into the body. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…ideo Output of the x-sdk-resource-driven generator for the non-fabric CRUD namespaces, plus a regenerated fabric module (PUT resources now carry _update_method = "PUT" rather than extending a separate FabricResourcePUT base). These modules are generated and mypy/ruff-clean but NOT yet wired into their namespace files — wiring (and deletion of the now-fully-generatable hand classes) follows once the per-resource sub-resource methods are generated too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
datasphere/relay-rest/video resource classes now include their declared sub-resource methods (search, list_chunks, list_members, list_streams, etc.) in addition to the base CRUD surface. Still generated-but-not-yet-wired; ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
Each generated resource class carries an __init__ setting its base URL path (namespace prefix + collection); construction is Resource(http). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…d-in paths - queues.py now re-exports the generated QueuesResource (the hand class is fully covered by the generated CRUD + sub-methods). - fabric.py: generated resource classes now bake their own base path into __init__, so construct them as Resource(http) (drop the path arg); the still-hand-written classes (call_flows/conference_rooms/subscribers/cxml_applications) keep (http, base). - Regenerated resource modules with the collection-relative / sibling path fix. All 766 rest tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
Replace the hand-written resource classes with the generated ones (which cover the full CRUD surface plus their declared sub-methods): - queues / number_groups / verified_callers: re-export the generated class. - datasphere: DatasphereNamespace constructs DatasphereDocumentsResource (back-compat alias DatasphereDocuments). - video: rooms / conferences use VideoRoomsResource / VideoConferencesResource (aliases VideoRooms / VideoConferences); the other video resources stay hand-written. - fabric: construct the generated resources as Resource(http) (they bake their own base path); hand group-B classes keep (http, base). The generated resource modules and all wired namespace files are ruff + mypy clean and GEN-FRESH passes. KNOWN: ~15 pre-existing CRUD tests fail because the generated typed create/sub-methods enforce the spec's required fields and field names, while the tests were written against the old **kwargs and used wrong/partial fields (e.g. phone_number vs the spec's number). These are replaced by the generated tests in the next step; the failures are stale tests, not wiring bugs (verified). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…roduct specs - _base.py: add ReadResource (list + get); CrudResource extends it. - logs.py: thin convenience namespace composing the generated per-product log resources (MessageLogsResource/VoiceLogsResource/FaxLogsResource from the messaging/voice/fax specs, ConferenceLogsResource from the logs spec). The hand classes are deleted; back-compat aliases kept. - Generated read-only / method-only resource modules. logs tests pass (read-only resources have no typed-create enforcement issue). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
addresses/recordings/short_codes/sip_profile/imported_numbers/mfa/lookup now generated (BaseResource + declared methods). Not yet wired. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
Generated from the calling spec's CallRequest discriminator mapping; each command is a
typed method posting {command, params, id?}. Not yet wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
The 7 call-routing convenience helpers (set_ai_agent/set_cxml_webhook/etc.) now generated as typed wrappers over update(). Not yet wired. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
registry (brands/campaigns/orders/numbers) and the project/chat/pubsub token resources now generated; create ops with union bodies carry a typed body param. Not yet wired. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
fabric group-B sub-methods, GenericResources/FabricAddresses/FabricTokens/ CxmlApplications, and the video sub-resources (sessions/recordings/tokens/streams/ conference_tokens) now generated. Not yet wired. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…nd classes) Replace the hand-written resource classes with the generated ones (which now cover the full surface including sub-methods and phone_numbers' set_* helpers): - addresses/recordings/short_codes/sip_profile/imported_numbers/lookup/mfa/ phone_numbers re-export from relay_rest_resources_generated. - chat/pubsub re-export from their generated modules. - project: ProjectNamespace constructs ProjectTokensResource (back-compat alias ProjectTokens). All routes resolve and methods are present (verified end-to-end). The remaining ~41 failing tests are all the benign typed-enforcement category (stale tests passing wrong/partial field names to now-typed methods) — no wiring/route bugs; replaced by generated tests in #10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…nd classes) - registry: RegistryNamespace constructs the 4 generated registry resources. - calling: re-export the generated CallingResource (37 command-dispatch methods); back-compat alias CallingNamespace. - video: VideoNamespace constructs all 7 generated video resources (rooms/conferences + room_tokens/room_sessions/room_recordings/conference_tokens/streams). - fabric: FabricNamespace constructs the generated group-B (call_flows/conference_rooms/ subscribers/cxml_applications) and root resources (resources/addresses/tokens); all hand resource classes deleted, back-compat aliases kept; list_addresses now routes the singular sub-path for call_flows/conference_rooms. All routes resolve and methods/sub-methods are present (verified end-to-end). The remaining failing tests are all stale tests of deleted hand-class behavior (typed- enforcement kwargs, removed deprecation warnings, the removed cxml_applications.create stub which never had a spec route) — no wiring/route bugs; replaced by generated tests in #10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
Regenerate with bare class names (Queues, Subscribers, VideoRooms, ... — no Resource suffix). Update the wiring (client.py + namespaces) and rest tests to the bare names. Delete all 21 hand-written back-compat aliases; the one kept name, CallingNamespace, is now a generated alias (x-sdk-resource.aliases on the calling resource). Full rest suite failure count unchanged (98, all the pre-existing benign typed- enforcement stale tests) — the rename introduced no new breakage. GEN-FRESH passes; ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…mespace alias - PhoneCallHandler is now generated from the relay-rest spec's call-handler enum (13 values, in sync with the wire) and re-exported from signalwire.rest; the hand-written call_handler.py (11 values) is deleted. - calling: drop the CallingNamespace alias; the resource is `Calling` everywhere (client.py constructs Calling; tests updated). Full rest suite failure count unchanged (98 — the pre-existing benign typed-enforcement stale tests); no new breakage. ruff + mypy clean; GEN-FRESH passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
client.py now inherits the generated _GeneratedResourceTree and calls _wire_resources() instead of 21 hand wiring lines — it owns only auth + the (pending-removal) compat namespace. The full tree (client.queues, client.fabric.ai_agents, client.video.rooms, client.logs.messages, ...) is generated from each resource's spec placement. Full rest suite failure count unchanged (98 benign); no new breakage. ruff + mypy clean; GEN-FRESH passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…erated tree) The hand namespace files (fabric.py/video.py/queues.py/...) — container classes and resource re-exports — are fully superseded by the generated _client_tree + the *_resources_generated modules, and nothing imports them anymore. Delete all 20; the one test reference (test_client) now imports the containers/Calling from the generated modules. namespaces/ is now generated modules + compat (the deliberate hand exception). Full rest suite failure count unchanged (98 benign); no new breakage. ruff + mypy clean; GEN-FRESH passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…rface Align the REST tests to the generated SDK surface (resources are now generated from the specs). Done via a 24-agent test-fix pass + the two generator fixes: - Wrong/renamed fields -> the spec's actual field name (domain->domain_identifier, friendly_name->name, code->verification_code, destination->dest, phone_number->number, token->refresh_token, embed_id->token, name->display_name, ...). - Partial bodies in error tests -> the spec's required fields with valid placeholders (the 4xx still comes from the pushed mock scenario). - Removed hand behavior -> removed obsolete deprecation-warning assertions and the cxml_applications.create stub (no spec route). - Generator-surfaced fields now reachable: Mfa.sms(from_), Calling.dial(to/from_/url/ codecs/...), Calling.update(id/status) — tests updated to the typed surface (from_ param -> wire "from"; status not state). - Addresses.create now sends the 9 spec-required fields. Full rest suite: 765 passed (was 98 failing). No spec edits — real spec gaps were logged (the SPEC_AUDIT_vs_RAILS.md findings), none papered over. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…mypy --strict Annotate the last non-generated rest code — the runtime substrate the generated resources sit on: - _base.py: HttpClient (_request/get/post/put/patch/delete -> Any wire JSON), BaseResource (__init__/_path), and the CRUD base methods (cast the Any wire returns to the bound TList/TItem). SignalWireRestError.__init__. - _pagination.py: PaginatedIterator fully annotated (also merged the stray double module docstring so imports sit at the top). - client.py: RestClient.__init__ signature. - core/logging_config.py: get_logger(name: str) -> Any (it was untyped and used by 31 files; fixed at the source rather than ignored in rest). - regenerated relay-rest (set_* helpers drop the now-redundant cast). Result: mypy --strict is clean across all 31 generated + wiring + substrate rest files (compat.py excluded — it is being removed in the Twilio-compat removal). The configured project mypy stays clean; the 765 rest tests still pass; ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…, tests) Delete the compat surface completely: - namespaces/compat.py (the 12 Compat* hand classes) and namespaces/compatibility_types_generated.py. - client.py: drop the CompatNamespace import + the self.compat construction; RestClient is now purely the generated resource tree + auth. - tests: delete all 15 test_compat_*.py files and the TestCompat class in test_namespaces.py; drop the compat references/examples in test_client.py and conftest. - _base.py: drop the compat-subclass rationale from the update() comment (the positional- only resource_id stays — harmless and keeps the oracle signature stable). Full rest suite: 506 passed (the ~259 compat tests removed). No functional compat remains anywhere; mypy --strict clean (30 files); GEN-FRESH passes. The only residual "compat" mentions are generated wire-description docstrings (laml_call value sources), which are correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…n); rest: regenerate - relay/call.py: collapse the ~14 repeated stop/pause/resume/volume action sub-methods into reusable bases parameterized by a _command_prefix class attr — StoppableAction (stop), PausableAction (+pause/resume), VolumeAction (+volume). Each *Action class now just declares its prefix + composes the right base (FaxAction sets the prefix per instance for send/receive). Behaviour preserved; the test asserting the old _method_prefix attr updated to _command_prefix. - rest: regenerate (no surface change from the BaseResource/default/positional generator work — default stays doc-only so the wire body is unchanged). mypy --strict clean on call.py + the generated modules; rest + relay suites green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…b typing) SWMLBuilder inherits the generated _SwmlVerbs Protocol under TYPE_CHECKING, so the verb methods it installs dynamically (from schema.json) are now statically typed — clears the verb-visibility strict errors (swml_builder 21 -> 7; remaining are the dynamic-creation internals, hand-annotation territory). Runtime behavior unchanged (Protocol is type-only); 416 SWML/builder tests + the full core/rest/relay suites green. Adds signalwire/core/swml_verbs_generated.py (generated from porting-sdk/schema.json). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…ld fix these A targeted pass over strict errors that no spec-generator would address: - abstract SWMLBuilder regression fixed (the _SwmlVerbs base is now a plain class with concrete bodies — see the generator commit). - Explicit re-exports (__all__) on _agent_host / _mixin_host so --strict no_implicit_reexport resolves AgentHost/_HostTyped (cleared the 9 attr-defined on the mixins). - type-arg (32 -> 0): bare dict/list/set/tuple/Callable/Pattern given their args across 16 files; auth_mixin gained its missing `from typing import Any`. - logging_config.py fully annotated (strip_control_chars/get_execution_mode/configure_logging/ the structlog processor + mode helpers) — a foundational file used by 31 modules, so its typed calls cascade-cleared a chunk of no-untyped-call package-wide. The remaining 238 are genuine framework-typing work (FastAPI route handlers + @tool/@app decorators + the deliberate _HostTyped TYPE_CHECKING/runtime-split tradeoff) — not spec-generatable, left for a dedicated framework-annotation effort. ruff clean; 5358 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…trict as the gate A 5-agent parallel burndown of the remaining strict errors, with REAL types — never papered with bare Any (dict[str,Any] only where the value is genuinely heterogeneous, the honest equivalent of TS's Record<string,unknown>). Corrects an earlier wrong claim that this framework code "couldn't be typed/generated": TypeScript passes full strict on the same surface (web/agent/swml) with only 3 `any` in its src, so it clearly can. Key fixes: - The @AgentBase.tool() decorator is now properly typed (TypeVar bound to Callable), clearing untyped-decorator cascades SDK-wide. The untyped-decorator errors were NOT structural — they were a missing-return-annotation cascade (proved: a typed-return FastAPI handler is clean). - FastAPI route-handler return annotations are RUNTIME-LOAD-BEARING (FastAPI builds a response model from them; a union return crashes startup). Resolved with an _as_response helper: internal _handle_* methods honestly return Response | dict[str, Any] (the dict is a real SWAIG/post-prompt passthrough that tests assert on); decorated route handlers funnel through _as_response to stay -> Response. No behavior change. - Explicit __all__ re-exports (_agent_host/_mixin_host) for no_implicit_reexport; the _HostTyped "cannot subclass Any" is the documented TYPE_CHECKING/runtime split -> scoped # type: ignore[misc] with reason. flask/flask-limiter (optional extras, no stubs) -> scoped # type: ignore[untyped-decorator]. - type-arg/redundant-cast cleared; logging_config fully annotated (cascade-cleared callers). - service_loader: bytes(result.body).decode() — a real latent bug (memoryview has no .decode()) that strict surfaced. Config: tool.mypy now `strict = true` (was a hand-picked subset) so the gate matches the bar. `python -m mypy` passes at 0; ruff clean; 5358 unit tests pass. Note: tests/ are NOT yet in the type-check scope (76k LOC, ~9570 strict errors) — deferred to the test-generation effort (#10) which will replace much of that surface with generated, already-typed code rather than hand-typing throwaway. Pre-existing broken example (bedrock_agent_run.py imports a never-defined run_agent) is unrelated, left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
When the REST layer became spec-generated, 20 hand namespace modules were deleted and the resource classes lost their *Resource/*Namespace suffixes (PhoneNumbersResource -> PhoneNumbers, CallingNamespace -> Calling, ...). client.<ns>.<resource> usage was unaffected, but direct imports broke: from signalwire.signalwire.rest.namespaces.phone_numbers import PhoneNumbersResource from signalwire.signalwire.rest.call_handler import PhoneCallHandler Re-add the 20 namespace modules + call_handler.py as thin RE-EXPORT STUBS (old name -> generated bare name) so those imports keep working. Each carries the `x-sdk-back-compat-shim` marker so the cross-port surface oracle skips it — these are PYTHON-ONLY and must NOT be added to other ports. (client.compat is deliberately NOT shimmed — that API was intentionally removed. AutoMaterializedWebhook has no equivalent and is not re-exported.) 506 rest tests pass; ruff + mypy clean; the shims add ZERO oracle surface (verified byte-identical with/without). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
Each Python-only shim (the 20 namespace re-export stubs + call_handler.py) now warns on import that the path is deprecated, pointing at client.<ns> (the supported access). Old imports keep working; normal client.<ns>.<resource> usage emits NO warning (the client tree never imports the shims). Message is path-accurate — it recommends client.<ns> rather than naming a per-symbol module (FabricResource lives in _base, others in *_resources_generated; client.<ns> is correct for all). Shims stay oracle-invisible (x-sdk-back-compat-shim). 506 rest tests pass; ruff clean; client.* usage verified warning-free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…enai spec) swaig_actions_generated.py — 27 typed action builders + the <Action> value TypedDicts, generated from porting-sdk/swaig-specs/swaig-response.yaml (vendored from mod_openai). The ergonomic FunctionResult methods will compose this typed layer. ruff + mypy --strict clean; runtime-verified to build correct wire output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…equest swaig_request_generated.py — the SwaigRequest TypedDict (+ SwaigArgument), generated from porting-sdk/swaig-specs/swaig-request.yaml (vendored from mod_openai). swaig_function.execute now takes raw_data: "SwaigRequest | None" (TYPE_CHECKING import — no runtime cost/cycle; a plain dict at runtime), replacing the bare dict[str, Any]. The inbound function-webhook payload is now statically typed where the handler receives it. ruff + mypy --strict clean; 1815 core tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
post_prompt_generated.py — the full post-prompt callback payload tree (PostPrompt envelope + the call_log role-union + swaig_log/times + nested sub-objects), generated from porting-sdk/swaig-specs/post-prompt.yaml (vendored from mod_openai). agent_base: - on_summary(summary: PostPromptData | None, raw_data: PostPrompt | None) — the user callback now receives a typed payload. - _find_summary_in_post_data(body: PostPrompt) — typed; the post_prompt_data.parsed/.raw reads type-check against the corrected extract_json shape. Behavior unchanged (annotations only; the legacy top-level `summary` fallback is preserved via an untyped probe). TYPE_CHECKING imports (no runtime cost/cycle). mypy --strict clean; 1815 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…ests Add the 19 generated <ns>_generated_test.py files (422 tests: success + error per route, asserting method/matched_route against the mock) and delete the 20 superseded hand test_*_full_mock.py files. The generated suite covers every route the hand full-mock tests covered (178) plus 31 more, captured from the real client + spec operationIds. Behavioral hand tests (pagination, response parsing, client construction, the per-namespace test_<ns>.py) are KEPT — they assert things beyond wire shape. 613 REST tests pass; 5465 unit tests pass overall. Generated files are ruff + mypy --strict clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…ests in the gate Drove mypy --strict to zero across the entire tests/ tree (was ~8950 errors) under the REAL whole-tree config, and added `tests` to [tool.mypy] files so the TYPECHECK gate now covers tests too (a new untyped test fails CI). - generated REST tests are strict-clean by construction; hand tests fully annotated with REAL types (-> None, typed fixtures, typed handlers) — never blanket Any. - mock-monkeypatch sites (mock.method = ...) carry # type: ignore[method-assign] + reason; intentional invalid-input tests carry # type: ignore[arg-type] + reason; stale ignores from an earlier --ignore-missing-imports pass removed (warn_unused_ignores keeps them honest). - real fixes surfaced by the strict pass: union-attr None-narrowing (assert), override-sig alignment, ClassVar/tuple/None-callable, generic type-args. - conftest.py + the 3 top-level tests/*.py fully typed; AgentBase imported from its canonical module so mypy resolves it. Source fix: prefabs (survey/receptionist/faq_bot/concierge) on_summary overrides updated to the new PostPromptData/PostPrompt base signature — the post-prompt retype had made them incompatible overrides (the whole-tree run caught this; source TYPECHECK would have gone red). Verified: `mypy --config-file pyproject.toml` = 0 across 338 files; 5523 unit tests pass (the one bedrock-example failure is pre-existing — imports a nonexistent run_agent). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…g is a fatal call error
The SWML `ai` verb's `prompt` must be an OBJECT — {"text": ...} or {"pom": [...]}. A bare string
is a FATAL error in the AI engine: mod_openai app_config.c does `if (!cJSON_IsObject(prompt))`
-> fires calling.error and aborts the call (verified against mod_infrastructure + mod_openai
source, not just the SDK schema). SWMLBuilder.ai() emitted `config["prompt"] = prompt_text`
(bare string) — and likewise post_prompt. Now wraps text -> {"text": ...}, pom -> {"pom": ...},
post_prompt -> {"text": ...}, matching the production AgentBase path (agent_base.py:1294) and
the engine.
Latent until now because the production render path builds the wrapped object itself and
bypasses SWMLBuilder.ai; only direct builder / SwmlRenderer.render_swml(str) callers hit it.
- update test_swml_builder assertions to the object form (they enshrined the bare-string bug
via a mock service that skips schema validation).
- render_swml tests now pass real `str`/`pom` args (11 of 12 # type: ignore[arg-type] dropped;
the remaining one is a legit None invalid-input test) — proving str -> valid SWML end-to-end.
Cross-port: flagged in porting-sdk GLOBAL_MEMORY (Go already fixed; .NET still has it; Contexts
path is the OPPOSITE contract — bare string is correct there, do not "fix").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…pdate)
Regenerated the REST resource modules — create/update/operation methods are now fully closed to
the spec fields (no `extras` param). Reserved-word fields like `from` are exposed as the typed
kwarg `from_` (mapped back to the wire key), so nothing is lost.
- test_small_namespaces_mock: mfa.call now passes from_="..." instead of extras={"from":...}.
mypy --strict clean (338 files); 5466 unit tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
…l-key door
Methods whose wire body has a reserved-word field (e.g. mfa.call's `from`) now expose both the
typed `from_` param AND a `**_reserved_kw` tail, so callers can pass the literal wire key via
`**{"from": ...}`. The `_reserved_kw` tail is dropped by the signature oracle (Python-only
workaround; surface stays closed cross-port).
mypy --strict clean; 613 REST tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
… create/update) create/update/operation methods carry the typed spec fields + extras + **kwargs again. The override-ignore is now emitted only where the signature genuinely narrows the base (required- param create / positional-body), so warn_unused_ignores stays satisfied. mypy --strict clean (338 files); 613 REST tests pass; 5466 unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jwtz9te6ivK5WgSJ61zXAF
e6ddefc to
09e3168
Compare
… scheduler run-ci gates now run through porting-sdk/scripts/gate_scheduler.sh: pure-Python side-effect-free gates run CONCURRENTLY (S2), heavy gates (TEST/build) deferred behind the cheap wave (S1 fail-fast). Data-deps honored (DRIFT deps=SIGNATURES; surface-mutating gates share res=surface); only genuinely-contending gates serialized (java res=gradle) — schedule-freely, throttle only what actually breaks. Per-gate PASS/FAIL + FAILED_GATES tally preserved; opt-in --fail-fast (default = full report). Gate set byte-identical to before (no gate dropped/added). Measured warm: ts 37->20s, go 17->5s, rust 94->30s; injected cheap-gate failure --fail-fast fails in ~0-2s vs full serial run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H8UmLE1BTztLFXspwbJWjj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds field-level static types to the REST/RELAY surface, generated mechanically from the canonical specs (the same approach TypeScript uses), with zero runtime change and zero cross-port drift.
41 commits · 263 files · +30.8k/−16.0k.
rest/namespaces/*_types_generated.py+relay/protocol_types_generated.py): oneTypedDict(total=False)percomponents/schemasentry + per-operation Request/Response aliases. 1270 generated types (875TypedDict+ 395TypeAlias). Emitted byporting-sdk/scripts/generate_python_rest_types.pyfromrest-apis/*/openapi.yaml+relay-protocol/*.json.fabric.list_versions() -> CallFlowVersionListResponse), guided by the TS port. Imported underTYPE_CHECKINGwith string forward-refs.extrasdoor + a**kwargstail (the kwargs-language idiom), with reserved-word fields reachable via the_reserved_kwliteral-key door. (This is the current head of the branch — the surface was briefly fully-closed mid-branch, then re-opened to the extras+kwargs model that matches the cross-port floor.)run-ci.sh:--checkfails if any committed generated module no longer reproduces from its spec (mirror of TS's--check).Runtime: unchanged
TYPE_CHECKING— never loaded at runtime (verified: absent fromsys.modules).TypedDictis a plaindictat runtime, so a differently-shaped server response is returned unchanged and never raises (total=False+ open shape).result["id"]) works identically. The types are additive IDE/mypy enrichment.Drift: zero, complex types preserved
All ports stay
drift=0. python and TS record the same named generated types (matched by leaf-name in the porting-sdk checker); the dict-recording ports (Gomap[string]any, etc.) match via the checker'sgen:X ≈ dictrule. No flattening todict[str, Any]— the field-level types are kept.Gates
mypy
--configzero · ruff format + check clean · GEN-FRESH pass · all ports drift=0.Dependency
Requires the porting-sdk changes (generator + checker normalization rules + GEN-FRESH script) on porting-sdk
mainfirst (#53), and the TS adapter alias-recording change (signalwire-typescript #140).🤖 Generated with Claude Code